tg-me.com/golangtests/764
Last Update:
Задача: Что выведет следующий код при компиляции и запуске с использованием Go 1.24?
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
count := 3
fmt.Println("Запуск горутин...")
wg.Add(count)
for i := 0; i < count; i++ {
// Запускаем горутину в каждой итерации
go func() {
defer wg.Done()
// Имитируем небольшую работу
time.Sleep(time.Duration(10) * time.Millisecond)
// Используем переменную цикла 'i' внутри горутины
fmt.Printf("Горутина видит i = %d\n", i)
}()
}
fmt.Println("Ожидание завершения горутин...")
wg.Wait()
fmt.Println("Все горутины завершены.")
}
Разбор:
Когда горутины реально начинали выполняться (после небольшой задержки time.Sleep), цикл for, скорее всего, уже завершался. К этому моменту переменная i имела бы свое конечное значение (в данном случае 3, так как цикл завершается, когда i становится равным count).
Поэтому в старых версиях Go этот код, скорее всего, вывел бы:
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 3
Горутина видит i = 3
Горутина видит i = 3
Все горутины завершены.
(Порядок строк "Горутина видит..." мог быть разным). Чтобы обойти это, раньше приходилось делать так: i := i внутри цикла перед запуском горутины, чтобы создать копию переменной для каждой итерации.
Новое поведение (Go 1.22 и новее, включая 1.24): Начиная с Go 1.22, семантика переменных цикла for была изменена для предотвращения этой распространенной ошибки. Теперь переменная цикла (i в нашем случае) пересоздается для каждой итерации.
Это означает, что каждая горутина захватывает свою собственную копию i, соответствующую значению на момент этой итерации.
Ожидаемый вывод (Go 1.24): Благодаря изменению в Go 1.22, каждая горутина теперь корректно видит значение i той итерации, в которой она была запущена. Поэтому вывод будет (порядок строк "Горутина видит..." может варьироваться из-за недетерминированного планирования горутин):
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 0
Горутина видит i = 1
Горутина видит i = 2
Все горутины завершены.
Use code with caution.
Или, например:
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 2
Горутина видит i = 0
Горутина видит i = 1
Все горутины завершены.
Подвох заключается в том, что код выглядит как классический пример ошибки захвата переменной цикла, но из-за изменений в языке начиная с Go 1.22, он теперь работает "правильно" без необходимости явного копирования переменной (i := i).